package goSocketTest

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
)

// 此处要设置有缓冲的通道。因为这是goroutine自己从通道中发送并接受数据。
// 若是无缓冲的通道，该goroutine发送数据到通道后就被锁定，需要数据被接受后才能解锁，而恰恰接受数据的又只能是它自己

var join = make(chan Client, 10)     // 用户加入通道
var leave = make(chan Client, 10)    // 用户退出通道
var message = make(chan Message, 10) // 消息通道

type Client struct {
	conn *websocket.Conn // 用户websocket连接
	name string          // 用户名称
}

// 1.设置为公开属性(即首字母大写)，是因为属性值私有时，外包的函数无法使用或访问该属性值(如：json.Marshal())
// 2.`json:"name"` 是为了在对该结构类型进行json编码时，自定义该属性的名称
type Message struct {
	EventType byte   `json:"type"`    // 0表示用户发布消息；1表示用户进入；2表示用户退出
	Name      string `json:"name"`    // 用户名称
	Message   string `json:"message"` // 消息内容
}

var clients = make(map[Client]bool) // 用户组映射

/**
广播 : 消息
*/
func broadcaster() {
	for {
		// 哪个case可以执行，则转入到该case。若都不可执行，则堵塞。
		select {
		// 消息通道中有消息则执行，否则堵塞
		case msg := <-message:
			// 将某个用户发出的消息发送给所有用户
			for client := range clients {
				// 将数据编码成json形式，data是[]byte类型
				// json.Marshal()只会编码结构体中公开的属性(即大写字母开头的属性)
				data, err := json.Marshal(msg.Message)
				if err != nil {
					fmt.Printf(" 编码消息失败 : %v", err)
					return
				}
				// fmt.Println("=======the json message is", string(data))  // 转换成字符串类型便于查看
				if client.conn.WriteMessage(websocket.TextMessage, data) != nil {
					fmt.Print(" 回应消息失败 ")
				}
			}

		// 有用户加入
		case client := <-join:
			str := fmt.Sprintf(" 新用户加入房间 , 用户名: %v ", client.name)
			fmt.Printf(str)

			clients[client] = true // 将用户加入映射

			// 将用户加入消息放入消息通道
			var msg Message
			msg.Name = client.name
			msg.EventType = 1
			msg.Message = client.name + " 加入房间 "

			// 此处要设置有缓冲的通道。因为这是goroutine自己从通道中发送并接受数据。
			// 若是无缓冲的通道，该goroutine发送数据到通道后就被锁定，需要数据被接受后才能解锁，而恰恰接受数据的又只能是它自己
			message <- msg

		// 有用户退出
		case client := <-leave:
			str := fmt.Sprintf("broadcaster-----------%v leave the chat room\n", client.name)
			fmt.Printf(str)

			// 如果该用户已经被删除
			if !clients[client] {
				fmt.Printf(" 该用户不存在 , 用户名为: %v" + client.name)
				break
			}

			delete(clients, client) // 将用户从映射中删除

			// 将用户退出消息放入消息通道
			var msg Message
			msg.Name = client.name
			msg.EventType = 2
			msg.Message = fmt.Sprintf("%v 退出房间, 还有 %v 人在房间 ", client.name, len(clients))
			message <- msg
		}
	}
}
